/*
 * @Description: 方法装饰器
 * @Autor: HuiSir<www.zuifengyun.com>
 * @Date: 2022-07-04 15:54:21
 * @LastEditTime: 2022-07-08 10:46:18
 */
import "reflect-metadata"
import useResult from '../useResult.js'
import { type IObj, type IData } from '../../common/types'
import {
    PROP_GROUP_METADATA_KEY,
    PROP_REQUIRE_METADATA_KEY,
    PROP_TYPE_METADATA_KEY,
    METHOD_DEC_METADATA_KEY,
    METHOD_DATA_CACH_METADATA_KEY,
    PARAMETR_METADATA_KEY,
    CLASS_SIGN_METADATA_KEY,
    METHOD_PARAMKEYARR_METADATA_KEY
} from '../../common/constants.js'

const { error } = useResult()

interface IDto {
    new(): Object
}

/**
 * 非方法使用验证，报错
 */
function targetVerify(desc: PropertyDescriptor) {
    if (desc == void 0 || !desc.value || typeof desc.value !== 'function') {
        throw new Error('This decorator is a method decorator and can only be used for methods.')
    }
}

/**
 * 缓存装饰器信息
 */
function defineMethodDecMetadata(target: IObj, propertyKey: string, methodDecKey: string) {
    const methodDecMetadata = Reflect.getMetadata(METHOD_DEC_METADATA_KEY, target, propertyKey) || []
    if (!methodDecMetadata.includes(methodDecKey)) {
        methodDecMetadata.push(methodDecKey)
        Reflect.defineMetadata(METHOD_DEC_METADATA_KEY, methodDecMetadata, target, propertyKey)
    } else {
        // 如果已经有了，不允许多次使用
        throw new Error(`The type of \`${methodDecKey}\` method decorator is already been used!`)
    }
}

/**
 * 缓存参数DATA
 * 由于修改方法之后，参数会改变，所以这里需要缓存一下原始参数
 */
function defineDataCach(target: IObj, propertyKey: string, data: any, key: string): IData {
    let dataCach = Reflect.getMetadata(METHOD_DATA_CACH_METADATA_KEY, target, propertyKey)
    if (!dataCach || dataCach.key === key) {
        Reflect.defineMetadata(METHOD_DATA_CACH_METADATA_KEY, { data, key }, target, propertyKey)
        dataCach = { data, key }
    }
    return dataCach.data
}

/**
 * 参数重置
 * 根据参数装饰器对方法参数进行重置
 */
function argumentsReset(target: IObj, key: string, data: IData) {
    // 获取参数装饰器元信息
    const parametersMetadata: { groupKey: string, field?: string }[] = Reflect.getMetadata(PARAMETR_METADATA_KEY, target, key)

    if (parametersMetadata) {
        return parametersMetadata.map(
            ({ groupKey, field }) => field && data[groupKey] ? data[groupKey][field] : data[groupKey]
        )
    } else {
        return [data]
    }
}


/**
 * 解析param参数
 */
function getParams(paramStr?: string) {
    if (paramStr != void 0) {
        if (!/^(\/:[a-zA-Z_]+[a-zA-Z0-9_]*)+$/.test(paramStr)) {
            throw new Error(`The parameter \`${paramStr}\` of method restrict decorator is in the wrong format! Refer to the rule \`^(\/:[a-zA-Z_]+[a-zA-Z0-9_]*)+$\`.`)
        } else {
            return paramStr.split("/:").slice(1)
        }
    } else {
        return false
    }
}

/**
 * 方法验证
 * 此装饰器必用
 */
function methodRestrict(m: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'SYS') {
    /**
     * 请求方式
     * @param paramStr param参数
     * @example `@Get()` `@Get('/:cat/:id')` `@Post()` `@Post('/:cat/:id')`
     */
    return (paramStr?: string) => {
        // 解析param参数
        const paramKeys = getParams(paramStr)
        return (target: IObj, propertyKey: string, desc: PropertyDescriptor) => {
            // 验证
            targetVerify(desc)
            // 缓存装饰器信息
            defineMethodDecMetadata(target, propertyKey, 'methodRestrict')
            // 缓存param参数
            paramKeys && Reflect.defineMetadata(METHOD_PARAMKEYARR_METADATA_KEY, paramKeys, target, propertyKey)
            // 修改原方法
            let curr = desc.value
            desc.value = (mydata: any) => {
                // 读取data
                const data = defineDataCach(target, propertyKey, mydata, 'methodRestrict')

                if (data.method.toUpperCase() !== m) {
                    return error({ status: 405, msg: 'Method Not Allowed' })
                }

                return curr.apply(target, argumentsReset(target, propertyKey, data))
            }
        }
    }
}

export default () => {
    return {
        /**
         * 请求方式:GET
         * @param paramStr param参数
         * @example `@Get()` `@Get('/:cat/:id')`
         */
        Get: methodRestrict('GET'),
        /**
         * 请求方式:Post
         * @param paramStr param参数
         * @example `@Post()` `@Post('/:cat/:id')`
         */
        Post: methodRestrict('POST'),
        /**
         * 请求方式:Put
         * @param paramStr param参数
         * @example `@Put()` `@Put('/:cat/:id')`
         */
        Put: methodRestrict('PUT'),
        /**
         * 请求方式:Delete
         * @param paramStr param参数
         * @example `@Delete()` `@Delete('/:cat/:id')`
         */
        Delete: methodRestrict('DELETE'),
        /**
         * 请求方式:Sys，为系统内置方法，仅限服务端内部调用
         * @param paramStr param参数，ps:`@Sys('/:cat/:id')`
         * @example `@Sys()` `@Sys('/:cat/:id')`
         */
        Sys: methodRestrict('SYS'),
        /**
         * @description: 参数绑定
         * @param Dto {IDto} 传参类
         * @author: HuiSir
         * @example `@Bind(Dto:IDto)`
         */
        Bind: (Dto: IDto) => {
            const { name, prototype } = Dto
            const classSign = Reflect.getMetadata(CLASS_SIGN_METADATA_KEY, prototype, name)

            // 非Dto类报错
            if (!classSign || classSign !== 'Dto') {
                throw new Error(`Class \`${name}\` is not a Dto class, please use the class decorator \`@Dto\`!`)
            }

            return (target: IObj, propertyKey: string, desc: PropertyDescriptor) => {
                // 验证
                targetVerify(desc)
                // 缓存装饰器信息
                defineMethodDecMetadata(target, propertyKey, 'bind')
                // 修改原方法
                let curr = desc.value
                desc.value = (mydata: any) => {
                    // 读取data
                    const data = defineDataCach(target, propertyKey, mydata, 'bind')

                    // 遍历属性类
                    const propertyNames = Object.getOwnPropertyNames(new Dto())
                    for (let prop of propertyNames) {
                        // 获取所属分组
                        const groupKey: string = Reflect.getMetadata(PROP_GROUP_METADATA_KEY, prototype, prop)

                        // 没有配置分组
                        if (!groupKey) {
                            return error({ status: 400, msg: `Parameter \`${prop}\` is not grouped!` })
                        }

                        // 必填状态
                        const requireStatus: boolean = Reflect.getMetadata(PROP_REQUIRE_METADATA_KEY, prototype, prop) || false
                        // 类型获取
                        const propType: string = Reflect.getMetadata(PROP_TYPE_METADATA_KEY, prototype, prop)
                        // 必填项验证
                        if (requireStatus && (!data[groupKey] || data[groupKey][prop] == void 0)) {
                            return error({ status: 400, msg: `Parameter \`${prop}\` is required!` })
                        }
                        // 类型错误验证
                        if (propType && data[groupKey] && data[groupKey][prop] != void 0 && data[groupKey][prop].constructor.name !== propType) {
                            return error({ status: 400, msg: `The parameter type of \`${prop}\` is wrong! Expected ${propType}, Actually ${data[groupKey][prop].constructor.name}.` })
                        }
                    }

                    return curr.apply(target, argumentsReset(target, propertyKey, data))
                }
            }
        }
    }
}